#include <buola/cv/barcode/decode.h>
#include <numeric>

namespace buola { namespace cvi { namespace barcode {

///get the width of a bar (or sum of bars) with respect to the base bar width)
///
///\param pW the width to measure
///\param p1200BarW 700 times the base bar width
///
///\return the rounded width of \p pW in bar units
static inline int get_width(int pW,int p1200BarW)
{
    return (1200*pW+p1200BarW/2)/p1200BarW;
}

///conversion of width groups to code points
static const int8_t sWidesToDigits[256]=
{
    -1, -1, -1,'%', -1, -1, -1,'T', -1, -1, -1, -1,'+', -1, -1,'G', -1, -1, -1,'7', -1, -1, -1, -1, -1,'S', -1, -1,'-', -1, 
   'R','Q', -1,'P', -1, -1, -1, -1,'O','N', -1, -1, -1, -1, -1, -1, -1, -1,'/', -1, -1, -1, -1, -1, -1, -1,'M','L', -1, -1,
    -1, -1,'K', -1, -1, -1, -1, -1, -1, -1, -1,'D', -1,'B', -1, -1, -1, -1,'A', -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1,'4','X', -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,'2','1', -1, -1, -1, -1, -1,
   'V', -1, -1, -1,'U', -1, -1, -1, -1, -1, -1,'0','*', -1, -1,'J', -1,'I', -1, -1, -1, -1,'H', -1, -1,'9','8', -1, -1, -1,
    -1, -1,' ', -1, -1, -1,'.', -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,'$','F', -1, -1, -1, -1,'E', -1,'C', -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,'Z','6','5', -1,'Y', -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   '3', -1, -1, -1, -1, -1, -1, -1,'W', -1, -1, -1, -1, -1, -1, -1
};

static inline int get_digit(const int *pPtr,int p1200BarW)
{
    int lCode=0;
    for(int i=0;i<8;i++)
    {
        lCode>>=1;
        if(get_width(10*(pPtr[i]+pPtr[i+1]),p1200BarW)<=25)
            lCode|=128;
    }
    return sWidesToDigits[lCode];
}

static inline int get_reverse_digit(const int *pPtr,int p1200BarW)
{
    int lCode=0;
    for(int i=0;i<8;i++)
    {
        lCode<<=1;
        if(get_width(10*(pPtr[i]+pPtr[i+1]),p1200BarW)<=25)
            lCode|=1;
    }
    return sWidesToDigits[lCode];
}

static inline std::string syms_to_text(const int *pSyms,int pN)
{
    std::string lText;
    lText.resize(pN-2,' ');
    for(int i=1;i<pN-1;i++)
    {
        lText[i-1]=pSyms[i];
    }
    
    if(pSyms[0]=='P')
        std::reverse(lText.begin(),lText.end());
    
    return lText;
}

void decode_code39(const std::vector<int> &pWidths,std::vector<CSymbol> &pSymbols)
{
    int lSyms[50];
    
    const int *lBegin=&pWidths.front();
    const int *lEnd=&pWidths.back();

    for(const int *lPtr=lBegin+1;lPtr<lEnd-8;lPtr+=2)
    {
        int l1200BarW=100*(lPtr[0]+lPtr[1]+lPtr[2]+lPtr[3]+lPtr[4]+lPtr[5]+lPtr[6]+lPtr[7]+lPtr[8]);
        //check guard to the left
        if(get_width(lPtr[-1],l1200BarW)<5) continue;

        lSyms[0]=get_digit(lPtr,l1200BarW);
        if(lSyms[0]!='*'&&lSyms[0]!='P') continue;

        int j=1;
        const int *lTmp;

        if(lSyms[0]=='P') //it's reversed, some things are a bit different
        {
            for(lTmp=lPtr+10;lTmp<lEnd-8&&j<50;lTmp+=10)
            {
                int lTmp1200BarW=100*(lTmp[0]+lTmp[1]+lTmp[2]+lTmp[3]+lTmp[4]+lTmp[5]+lTmp[6]+lTmp[7]+lTmp[8]);
                if(lTmp1200BarW*9>l1200BarW*10||lTmp1200BarW*10<l1200BarW*9) break;
                lSyms[j]=get_reverse_digit(lTmp,lTmp1200BarW);
                if(lSyms[j]<0) break;
                j++;
                l1200BarW=lTmp1200BarW;
            }
            if(lSyms[j-1]!='*') continue;
        }
        else
        {
            for(lTmp=lPtr+10;lTmp<lEnd-8&&j<50;lTmp+=10)
            {
                int lTmp1200BarW=100*(lTmp[0]+lTmp[1]+lTmp[2]+lTmp[3]+lTmp[4]+lTmp[5]+lTmp[6]+lTmp[7]+lTmp[8]);
                if(lTmp1200BarW*9>l1200BarW*10||lTmp1200BarW*10<l1200BarW*9) break;
                lSyms[j]=get_digit(lTmp,lTmp1200BarW);
                if(lSyms[j]<0) break;
                j++;
                l1200BarW=lTmp1200BarW;
            }
            if(lSyms[j-1]!='*') continue;
        }
        
        if(j<2) continue;
        
        CSymbol lSym;
        lSym.mB=std::accumulate(lBegin,lPtr,0)>>5;
        lSym.mE=lSym.mB+(std::accumulate(lPtr,lTmp-1,0)>>5);
        lSym.mText=syms_to_text(lSyms,j);
        lSym.mType=EType::CODE39;
        lSym.mDirection=(lSyms[0]=='P'?-1:1);

        pSymbols.push_back(lSym);
        lPtr=lTmp;
    }
}
    
/*namespace barcode*/ } /*namespace cvi*/ } /*namespace buola*/ }
